package uk.ac.ebi.dod.beans;

import uk.ac.ebi.dod.utils.ScriptUtil;
import uk.ac.ebi.dod.utils.LogRedirector;

import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
import javax.faces.context.FacesContext;
import javax.faces.application.FacesMessage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Florian Reisinger
 * @since 0.1
 */
public class ConfigCollector {

    private static final Log log = LogFactory.getLog(ConfigCollector.class);

    public static final String FILTER_NAME_ACC = "accession";
    public static final String FILTER_LABL_ACC = "SwissProt accession";
    public static final String FILTER_NAME_KWD = "keyword";
    public static final String FILTER_LABL_KWD = "UniProt keyword";
    public static final String FILTER_NAME_TAX = "TaxID";
    public static final String FILTER_LABL_TAX = "NCBI TaxID";
    public static final String FILTER_NAME_MAT = "maturation";
    public static final String FILTER_LABL_MAT = "Maturation";

    private InputSource selectDB;
    private List<SelectItem> sourceDBSelectList;
    private List<InputSource> inputDBs;

    private List<SelectItem> filterSelectList;
    private List<Filter> filterList;

    private int enzymeMethod;
    private Enzyme selectedEnzyme;
    private String enzymeName;
    private String enzymeCleave;
    private String enzymeRestrict;
    private int enzymeMisCleav;
    private int enzymeCterm;
    private List<SelectItem> enzymeSelectList;
    private List<Enzyme> enzymeList;

    private boolean maturation;
    private int maturationMethod;
    private boolean ragging;
    private int raggingMethod;
    private int raggingLimit;
    private boolean truncate;

    private boolean useSequenceFilter;
    private String sequenceFilter;
    private boolean useMassLimit;
    private int massLimitStart;
    private int massLimitEnd;

    private boolean showReport;
    private String report;
    private String workflowScript;

    private String reportMessage;
    private String reportView;

    private String email;

    private ScriptUtil util;


    ///// ///// ///// ///// /////
    // CONSTRUCTOR

    public ConfigCollector() {
        // activate the redirection of the logging to log4j
        LogRedirector.activate();
        log.info("--------------------------------------------------------------------------------");
        log.info("Creating new ConfigCollector.");
        initFilterSelectList();
        initSourceDbSelectList();
        initEnzymeSelectList();
        resetAll(); // reset/init all variable fields
        log.trace("Created new ConfigCollector.");
    }

    ///// ///// ///// ///// /////
    // INITIALIZER

    private void initSourceDbSelectList() {
        sourceDBSelectList = new ArrayList<SelectItem>();
        List<InputSource> sources = createInputDBs();
        for (InputSource source : sources) {
            sourceDBSelectList.add( new SelectItem(source, source.getSourceDBName()) );
        }
    }

    private void initFilterSelectList() {
        filterSelectList = new ArrayList<SelectItem>();
        List<Filter> filters = createFilterList();
        for (Filter filter : filters) {
            filterSelectList.add( new SelectItem(filter, filter.getLabel()) );
        }
    }

    private void initFilter() {
        this.filterList = null;
    }

    private void initEnzymeSelectList() {
        enzymeSelectList = new ArrayList<SelectItem>();
        List<Enzyme> enz = createEnzymeList();
        for (Enzyme enzyme : enz) {
            enzymeSelectList.add( new SelectItem(enzyme, enzyme.getName()) );
        }
    }

    private void initCustomExzyme() {
        this.enzymeName = "name";
        this.enzymeCleave = "cleave";
        this.enzymeRestrict = "restrict";
        this.enzymeMisCleav = 0;
        this.enzymeCterm = 0;
    }

    private void initMaturation() {
        this.maturationMethod = 0;
        this.maturation = false;
    }

    private void initRaggingValues() {
        this.ragging = false;
        this.raggingLimit = 100;
        this.raggingMethod = 1; // 0 = C terminal ragging; 1 = N terminal ragging
        this.truncate = false;
    }

    private void initPeptideFilters() {
        this.useSequenceFilter = false;
        this.sequenceFilter = null;
        this.useMassLimit = true;
        this.massLimitStart = 600;
        this.massLimitEnd = 5000;
    }

    private void initReport() {
        this.showReport = false;
        this.report = null;
        this.workflowScript = null;
    }

    private void resetAll() {
        log.debug("Resetting all values");
        if (inputDBs != null) {
            for (InputSource inputDB : inputDBs) {
                inputDB.reset();
            }
        }
        this.inputDBs = null;
        this.selectDB = null;
        this.enzymeList = null;
        this.selectedEnzyme = null;
        this.enzymeMethod = 0;
        initCustomExzyme();   // enzymeName, enzymeCleave, enzymeRestrict, enzymeCterm, enzymeMisCleav
        initFilter();         // filterList, filter, filterParam
        initMaturation();     // maturationMethod, maturation
        initRaggingValues();  // ragging, raggingLimit, raggingMethod, truncate
        initPeptideFilters(); // useSequenceFilter, sequenceFilter, useMassLimit, massLimitStart, massLimitEnd
        initReport();         // showReport, report, workflowScript
        this.email = null;
        this.util = null;

        this.reportMessage = null;
        this.reportView = null;
    }

    private List<Enzyme> createEnzymeList() {
        List<Enzyme> eList = new ArrayList<Enzyme>();



        // Title:Trypsin
        // Cleavage:KR
        // Restrict:P
        // Cterm
        Enzyme tmp = new Enzyme("Trypsin", "KR", "P", true);
        tmp.setMisCleavages(1);
        eList.add(tmp);

        //Title:Trypsin/P
        //Cleavage:KR
        //Cterm
        tmp = new Enzyme("Trypsin/P", "KR", null, true);
        tmp.setMisCleavages(1);
        eList.add(tmp);
        
        //Title:Arg-C
        //Cleavage:R
        //Restrict:P
        //Cterm
        tmp = new Enzyme("Arg-C", "R", "P", true);
        tmp.setMisCleavages(1);
        eList.add(tmp);

        //Title:Arg-C/P
        //Cleavage:R
        //Cterm
        tmp = new Enzyme("Arg-C/P", "R", null, true);
        tmp.setMisCleavages(1);
        eList.add(tmp);

        //Title:Lys-N
        //Cleavage:K
        //Nterm
        tmp = new Enzyme("Lys-N", "K", null, false);
        tmp.setMisCleavages(1);
        eList.add(tmp);

        //Title:Lys-C
        //Cleavage:K
        //Restrict:P
        //Cterm
        tmp = new Enzyme("Lys-C", "K", "P", true);
        tmp.setMisCleavages(1);
        eList.add(tmp);

        //Title:Lys-C/P
        //Cleavage:K
        //Cterm
        tmp = new Enzyme("Lys-C/P", "K", null, true);
        tmp.setMisCleavages(1);
        eList.add(tmp);

        //Title:Chymotrypsin
        //Cleavage:FYWLIVM
        //Restrict:P
        //Cterm
        tmp = new Enzyme("Chymotrypsin", "FYWLIVM", "P", true);
        tmp.setMisCleavages(1);
        eList.add(tmp);

        return eList;
    }

    private List<Filter> createFilterList() {
        List<Filter> list = new ArrayList<Filter>();

        Filter f = new Filter();
        f.setName(FILTER_NAME_ACC);
        f.setLabel(FILTER_LABL_ACC);
        f.setParameter(null);
        f.setEnableParamInput(true);
        list.add(f);

        f = new Filter();
        f.setName(FILTER_NAME_KWD);
        f.setLabel(FILTER_LABL_KWD);
        f.setParameter(null);
        f.setEnableParamInput(true);
        list.add(f);

        f = new Filter();
        f.setName(FILTER_NAME_TAX);
        f.setLabel(FILTER_LABL_TAX);
        f.setParameter(null);
        f.setEnableParamInput(true);
        list.add(f);

        f = new Filter();
        f.setName(FILTER_NAME_MAT);
        f.setLabel(FILTER_LABL_MAT);
        f.setParameter(null);
        f.setEnableParamInput(false);
        list.add(f);

        return list;
    }

    private List<InputSource> createInputDBs() {
        List<InputSource> list = new ArrayList<InputSource>();
        InputSource is = new InputSource();

        // UniProt resources
        is.setSourceDBName("SwissProt");
        is.setSourceLocation("/ebi/ftp/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_sprot.dat.gz");
        is.setType("selected");
        list.add(is);
        is = new InputSource();
        is.setSourceDBName("TrEMBL");
        is.setSourceLocation("/ebi/ftp/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_trembl.dat.gz");
        is.setType("selected");
        list.add(is);

        // IPI resources
//        is = new InputSource();
//        is.setSourceDBName("IPI HUMAN");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/current/ipi.HUMAN.dat.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("IPI RAT");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/current/ipi.RAT.dat.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("IPI MOUSE");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/current/ipi.MOUSE.dat.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("IPI CHICKEN");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/current/ipi.CHICK.dat.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("IPI COW");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/current/ipi.BOVIN.dat.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("IPI ARABIDOPSIS");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/current/ipi.ARATH.dat.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("IPI ZEBRAFISH");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/current/ipi.DANRE.dat.gz");
//        is.setType("selected");
//        list.add(is);

        // MSIPI resources
//        is = new InputSource();
//        is.setSourceDBName("MSIPI HUMAN");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/msipi/current/msipi.HUMAN_slim.fasta.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("MSIPI HUMAN Decoy");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/msipi/current/msipi.HUMAN_decoy_slim.fasta.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("MSIPI MOUSE");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/msipi/current/msipi.MOUSE_slim.fasta.gz");
//        is.setType("selected");
//        list.add(is);
//        is = new InputSource();
//        is.setSourceDBName("MSIPI MOUSE Decoy");
//        is.setSourceLocation("/net/nas14a/vol1/databases/IPI/msipi/current/msipi.MOUSE_decoy_slim.fasta.gz");
//        is.setType("selected");
//        list.add(is);

        // testcase only!
//        is = new InputSource();
//        is.setSourceDBName("uniprot-test");
//        is.setSourceLocation("/ebi/production/seqdb/sp/database-on-demand/tmpSources/uniprot_sprot-snip1.dat");
//        is.setType("selected");
//        list.add(is);
        return list;
    }

    ///// ///// ///// ///// /////
    // METHODS

    public void addSource(ActionEvent e) {
        initReport();
        InputSource db = selectDB;
        // now add the DB to the list of input DBs
        if (inputDBs == null) {
            inputDBs = new ArrayList<InputSource>();
        }
        if(!inputDBs.contains(db)) {
            inputDBs.add(db);
        } else {
            //@TODO Open up a little box or message saying that the input DB is already selected, and can be updated in the table.
            log.debug("DB '" + db.getSourceDBName()  + "' already in table, you moron!");
        }
    }

    public void addEnzyme(ActionEvent e) {
        initReport();

        if (enzymeList == null) {
            enzymeList = new ArrayList<Enzyme>();
            log.trace("Created new enzyme list: " + enzymeList);
        }
        Enzyme tmp;
        if (enzymeMethod == 0) {
            tmp = selectedEnzyme;
        } else if (enzymeMethod == 1) {
            // ToDo: check if input parameters valid
            tmp = new Enzyme(enzymeName, enzymeCleave, enzymeRestrict, (enzymeCterm == 0));
        } else {
            throw new IllegalStateException("Not a valid enzyme specification method!");
        }
        tmp.setType(enzymeMethod);
        tmp.setMisCleavages(enzymeMisCleav);
        tmp.setParent(enzymeList);
        enzymeList.add(tmp);
        initCustomExzyme(); // reset values for display
        log.debug("Added enzyme [" + tmp.getName() + "] to enzyme list: " + enzymeList);
    }

    public void emzymeSelectionChange(ValueChangeEvent e) {
        log.trace("ValueChangeEvent: Changing enzyme values!");
        Object o = e.getNewValue();
        Enzyme enz;
        if ( o instanceof Enzyme) {
            enz = (Enzyme) o;
            this.enzymeName = enz.getName();
            this.enzymeCleave = enz.getCleavePattern();
            this.enzymeRestrict = enz.getRestrictors();
            this.enzymeMisCleav = enz.getMisCleavages();
            this.enzymeCterm = enz.isCterm() ? 0 : 1;
            log.trace("setting values to: " + enzymeName + ":" + enzymeCleave + ":" + enzymeRestrict + ":" + enzymeMisCleav + ":" + enzymeCterm);
        } else {
            this.enzymeName = "";
            this.enzymeCleave = "";
            this.enzymeRestrict = "";
            this.enzymeMisCleav = 0;
            this.enzymeCterm = 0;
            log.trace("No Enzyme selected! Probably the title.");
        }

    }

    // reset report on changed values
    public void resetReportListener(ValueChangeEvent e) {
        log.trace("Values have changed. Resetting report.");
        initReport();
    }

    // reset report on actions
    public void resetReportActionListener(ActionEvent e) {
        log.trace("Reset report action listener was called.");
        initReport();
    }

    public void clearAll(ActionEvent e) {
        resetAll();
    }

    public void generateWorkflow(ActionEvent e) {
        reportView = calculateReportView();
        if (inputDBs != null && inputDBs.size() > 0) {
            // erase previous values
            initReport();
            log.info("Generating workflow.");
            // generate the report and set the internal field
            this.report = generateReport();
            // the the showReport flag to 'true' so the report is displayed
            this.showReport = true;
            log.debug("Set show report flag, so report will be displayed.");
        } else {
            log.info("Attempt to create workflow without any input source.");
        }
    }

    public void generateWorkflowForward(ActionEvent e) {
        log.debug("Setting 'reportView' to 'ok' 'showReport' = " + showReport);
        reportView = "ok";
    }

    public void submitScript(ActionEvent e) {
        util = new ScriptUtil();
        log.info("Submitting script with ID '" + util.uniqueID + "' and hand over to controller script.");
        log.info("A confirmation email will be send to: " + email);

        // now we are getting serious
        // check the destination folders are in place
        int status = util.checkFS(false);
        if (status < 0) {
            // ups, something gone wrong
            log.error("Expected filesytem structure not found! Error code or FS check: " + status);
            // ToDo: provide user feedback (pop-up window, message, ...)
            // ToDo: analyse error in more detail
        } else { // everything ok so far
            // since we are actually submitting, we can create the temporary working folder
            boolean buildFolder = util.createTempJobFolder();
            log.debug("Created temp working folder? " + buildFolder);
            if (buildFolder) {
                // generate the script that is to be executed
                log.info("Generating script.");
                this.workflowScript = generateScript();

                // copy needed files into work directory
                try {
                    // write script to a file in the working directory (via ScriptUtil)
                    util.writeScriptToTmpDest(workflowScript);
                    // write the used enzymes (if any) as enzymes.txt file to working directory (via ScriptUtil)
                    if ( enzymeList != null && enzymeList.size() > 0 ) {
                        util.writeEnzymesFileToTmpDest(enzymeList);
                    }
                    // also write the report which will be send to the user
                    // (incudes the URL where to download the result)
                    util.writeSuccessHeaderToTmpDest(null);
                    util.writeReportToTmpDest(report);
                } catch (IOException ioe) {
                    // ToDo: add reasonable actions: user feedback, cancel job, ...
                    log.error("IOException trying to write to temporary working directory.", ioe);
                }

                // execute the workflow (activate the controller script which will send the job to LSF)
                try {
                    util.executeWorkflow();
                } catch (Exception te) {
                    log.error("ERROR trying to execute the workflow!", te);
                }
            } else {
                log.error("Could not create temporary working directory!");
            }
        }
        
        // finally reset everything
        resetAll();
        log.info("Current workflow submitted and all values reset. Ready for the next job.");
    }

    // prototype to show message on page
    public void filterHelpAction(ActionEvent e) {
        // ToDo: detail is not shown
        // ToDo: how to select the corect <tr:message> location
        log.debug("Showing message on page.");
        FacesContext facesContext = FacesContext.getCurrentInstance();
        FacesMessage message = new FacesMessage();
        message.setSeverity(FacesMessage.SEVERITY_INFO);
        message.setSummary("Simple choose the filter you want to use.");
        message.setDetail("Detailed documentation for each filter can be found here: http://www.enfin.org.\n As a quick help: try this...");
        facesContext.addMessage("filter help message", message); 
        facesContext.renderResponse();
    }

    private String calculateReportView() {
        log.info("Generation of workflow requested. Checking which report view to show.");
        StringBuilder sb = new StringBuilder();
        boolean error = false;
        boolean warn = false;

        // we build up 'ERROR' or 'WARNING' messages as we go along

        // first (and most important check 
        if (inputDBs == null || inputDBs.size() < 1) {
            sb.append("\nERROR: You have not specified any input database!\n");
            sb.append("    Please add (click the 'Add as source' button) at least one input database and try again.\n");
            error = true;
        } else {
            // we have at least one input DB, now check if we have any filters associated
            for (InputSource inputDB : inputDBs) {
                if (inputDB.getFilterSize() < 1) {
                    sb.append("\nWARNING: You have not specified any filter for the input '").append(inputDB.getSourceDBName());
                    sb.append("'!\n    Please check carefully if you really want to use this resource unfiltered.\n");
                    warn = true;
                }
            }
        }

        if (enzymeList == null || enzymeList.size() < 1) {
            sb.append("\nWARNING: You have not specified any enzyme! The database will not be digested.\n");
            warn = true;
        }

        // ToDo: more checks

        // add a information

        // no determine the return value
        String retVal;

        if (error) { // overwrite with 'error' if needed
            retVal = "error";
            sb.append("\n\nINFO: The form is lacking neccessary information.\n");
            sb.append("    Please correct the errors and click the 'Generate workflow' button again.\n");
        } else if (warn) { // overwrite with 'warn' if needed
            retVal = "warn";
            sb.append("\n\nINFO: Your input may be incomplete. Please check your selected values carefully.\n");
            sb.append("    If you feel certain that your workflow is correct, you can proceed clicking\n");
            sb.append("    on the 'Generate anyway' button.\n");
            sb.append("    If you want to change your input, please do so and click the 'Generate workflow'\n");
            sb.append("    button again. This view will then be updated.");
        } else {
            retVal = "ok";
        }

        reportMessage = sb.toString();
        log.debug("Showing report view: " + retVal);
        return retVal;
    }

    private String generateScript() {
        log.info("Generating workflow script.");
        // initial checking
        // ToDo: extend this, maybe even in separate method
        if (inputDBs == null || inputDBs.size() < 1) {
            // we can not do anything if we don't have at least one input DB!!
            // ToDo: checked exception? at least notification/logging
            log.error("Attempt to build execution script without a single input DB! This should not be allowed by the webapp!");
            throw new IllegalStateException("A DB generation script without a single input DB is pointless!");
        }

        // this StringBuilder will accumulate the commands until the DB generation script is complete
        StringBuilder sb = new StringBuilder();

        // first add a header (to define the type of script and load environment variables)
        sb.append(util.getScriptHeader());
        sb.append("\n"); // add a empty line for readability

        sb.append("# define the sub-routies for the script\n");

        // then add the success and failure procedures that are the same for all the scripts
        sb.append( util.buildOnSuccessProcedure(email) );
        sb.append("\n"); // add a empty line for readability
        sb.append( util.buildOnFailureProcedure(email) );
        sb.append("\n"); // add a empty line for readability
        sb.append("\n"); // add a empty line for readability

        sb.append("# perform the following steps to generate the custom DB\n");

        // first thing the script should do is to send a submission confirmation email to the user
        sb.append( util.sendConfirmEmailCmd(email) );

        // we need to determine if we have to bring the DBs to the same format manually or if that has
        // automatically happened through the filtering
        boolean needsFastaConversion = isFastaConversionNeeded(inputDBs);

        // now for each input DB we apply all associated filters, which will result in one
        // output DB per input DB, therefore we have to keep track of all of them
        List<String> outFiles = new ArrayList<String>();
        for (InputSource inputDB : inputDBs) {
            // copy the original input DB to a temp directory for further process
            sb.append( util.buildTmpDirCmd() );
            sb.append( util.copyToTempDestCmd(inputDB.getSourceLocation(), inputDB.getSourceDBName()) );
            String tmp = inputDB.getSourceDBName();
            // if we have any filter steps to do with this DB
            List<Filter> inputFilters = inputDB.getFilters();
            if ( inputFilters != null && inputFilters.size() > 0 ) {
                tmp += ".f"; // name of the new filtered DB
                // construct the parameter String for all the filters
                String filterSetParam = util.buildFilterSetParam(inputFilters);

                // now build the command line for the filtering step (distinguish between maturation and none-maturation)
                if (containsMaturation(inputFilters)) {
                    sb.append( util.buildMaturationCmd(filterSetParam, inputDB.getSourceDBName(), tmp) );
                } else {
                    sb.append( util.buildFilterCmd(filterSetParam, inputDB.getSourceDBName(), tmp) );
                }

                // add a exit status check for the previous command, so we can abort if necessary
                sb.append( util.buildExitStatusCheckIf() );
            } else {
                // if there are no filters for this resource, we have to at least change it to unzipped FASTA format
                if (needsFastaConversion) {
                    tmp += ".f";
                    sb.append( util.buildFastaOutputCmd(inputDB.getSourceDBName(), tmp) );
                    sb.append( util.buildExitStatusCheckIf() );
                }
            }
            outFiles.add(tmp);
        }

        // the following steps can also perform a mass filter for peptides (which will optimise resources)), but
        // if non of the next steps performs a mass filtering, we have to do it in a separate step at the end
        boolean massFilterDone = false; // ToDo: check if additional mass filter at end is necessary (if not all steps have mass filtered)

        // combine the result DBs from the previous step (if there are more than one)
        Iterator<String> iter = outFiles.iterator();
        String finalConcatDB = iter.next(); // first file
        String outFile;
        while (iter.hasNext()) { // add other files if any
            outFile = iter.next();
            sb.append( util.buildCatCmd(finalConcatDB, outFile) ); // add the first fo the next file
            finalConcatDB = outFile; // the next file become the first
        }
        // finalConcatDB is the final result file of this step



        boolean needsRedundancyClean = false;
        // for each enzyme run EnzymeDigest passing on the output as input to the next round
        String tmpFile_1 = finalConcatDB;
        if (enzymeList != null && enzymeList.size() > 0) {
            // we will do a enzymatic digest, we should also perform a redundancy cleaning step at the end
            needsRedundancyClean = true;  // we have at least one enzyme, so we will perform a digest and we should clear redundancies
            String tmpFile_2;
            for (Enzyme enzyme : enzymeList) {
                tmpFile_2 = tmpFile_1 + ".d";
                if (useMassLimit) {
                    massFilterDone = true; // record that we did mass filtering
                    // perform digestion with mass filtering
                    sb.append( util.buildDigestCmd(enzyme.getName(), enzyme.getMisCleavages(), massLimitStart, massLimitEnd, tmpFile_1, tmpFile_2) );
                } else {
                    // perform digestion without mass filtering
                    sb.append( util.buildDigestCmd(enzyme.getName(), enzyme.getMisCleavages(), -1, -1, tmpFile_1, tmpFile_2) );
                }
                // add a exit status check for the previous command, so we can abort if necessary
                sb.append( util.buildExitStatusCheckIf() );
                tmpFile_1 = tmpFile_2;
            }
        } else {
            // no digestion! quite unusual, no?
            log.info("No enzymatic digest requested!");
        }
        String finalDigestDB = tmpFile_1;

        // call ProteinMaturationDevice
        String finalPmdDB = finalDigestDB;
        if (maturation) {
            finalPmdDB += ".pmd";  // we create a new result file
            sb.append( util.buildPMDCmd(maturationMethod, finalDigestDB, finalPmdDB) );
            // add a exit status check for the previous command, so we can abort if necessary
            sb.append( util.buildExitStatusCheckIf() );
        }

        // ToDo: check if we use ragging or not (if not we have to use the mass filter with some other option)

        // call RagDB with ragging parameters and mass filter (if requested)
        String finalRagDB = finalPmdDB; // in case we don't perform this step we pass on the input file
        if (ragging) {
            int truncateLimit = -1;
            if (truncate) { truncateLimit = raggingLimit; }
            finalRagDB +=  ".rag"; // we create a new result file
            if (useMassLimit) {
                massFilterDone = true;
                sb.append( util.buildRagCmd(raggingMethod, truncateLimit, massLimitStart, massLimitEnd, finalPmdDB, finalRagDB) );
            } else {
                sb.append( util.buildRagCmd(raggingMethod, truncateLimit, -1, -1, finalPmdDB, finalRagDB) );
            }
            // add a exit status check for the previous command, so we can abort if necessary
            sb.append( util.buildExitStatusCheckIf() );
        } else {
            // no ragging and no mass fitering! nothing to do.
        }

        // call IsolateSubset with sequence query string (if requested)
        String finalSeqFilterDB = finalRagDB;
        if (useSequenceFilter) {
             finalSeqFilterDB += ".seqFilter"; // we create a new result file
            if (useMassLimit) {
                // use sequence filter with mass filtering
                massFilterDone = true;
                // ToDo: does that make sense here? has the mass filter been used before?
                sb.append( util.buildSeqFilterCmd(sequenceFilter, massLimitStart, massLimitEnd, finalRagDB, finalSeqFilterDB) );
            } else {
                // use sequence filter without mass filtering
                sb.append( util.buildSeqFilterCmd(sequenceFilter, -1, -1, finalRagDB, finalSeqFilterDB) );
            }
            // add a exit status check for the previous command, so we can abort if necessary
            sb.append( util.buildExitStatusCheckIf() );
        } else {
            // no peptide sequence filtering! nothing to do.
        }

        // now check if we did the mass filtering
        // in case that we did not apply processing steps that can also filter by mass, we might still have to do it!
        String finalMassFilterDB = finalSeqFilterDB;
        if (useMassLimit && !massFilterDone) {
            finalMassFilterDB += ".mass";
            // we were requested to filter by mass, but did not filter all sources yet!
            sb.append( util.buildMassFilterCmd(massLimitStart, massLimitEnd, finalSeqFilterDB, finalMassFilterDB) );
            // add a exit status check for the previous command, so we can abort if necessary
            sb.append( util.buildExitStatusCheckIf() );
        }

        // as final processing step we should run a "clear redundancy" (at least if we did a digest)
        String finalCleanedDB = finalMassFilterDB;
        if (needsRedundancyClean) {
            finalCleanedDB += ".cln";
            sb.append( util.buildClearRedundancyCmd(finalMassFilterDB, finalCleanedDB) );
            // add a exit status check for the previous command, so we can abort if necessary
            sb.append( util.buildExitStatusCheckIf() );
        }

        // rename the last file (whatever its name is to the final name we want it to have)
        sb.append("\n"); // add a empty line to the script for readability
        sb.append( util.renameToFinalNameCmd(finalCleanedDB) );

        // if all went ok, we finally can call the success procedure
        sb.append( util.buildSuccessCmd() );

        log.info("Workflow script successfully created.");
        return sb.toString();
    }

    private boolean isFastaConversionNeeded(List<InputSource> inputDBs) {
        log.trace("Checking whether we need a conversion of the databases to FASTA.");
        // as soon as we find that we have differently treated DBs we will have to convert all of them
        // that mean if we have at least one DB which is filtered (and therefore unzipped FASTA format)
        // and at least one DB without filter (and therefore still zipped dat format)
        // then we will need to convert all to nonzipped FASTA format.
        boolean withFilter = false;
        boolean withoutFilter = false;
        for (InputSource inputDB : inputDBs) {
            if (inputDB.getFilterSize() > 0 ) {
                withFilter = true;
            } else {
                withoutFilter = true;
            }
            // check if we already have differently treated DBs (and stop checking if so)
            if (withFilter && withoutFilter) {
                return true;
            }
        }

        // if we ran through all input DBs and have not found differently treated DBs, we can
        // return false (no conversion needed)
        return false;
    }

    private boolean containsMaturation(List<Filter> inputFilters) {
        log.trace("Checking of the filters contain a Maturation step.");
        boolean retVal = false;
        for (Filter filter : inputFilters) {
            if (filter.getName().equalsIgnoreCase(FILTER_NAME_MAT)) {
                return true;
            }
        }
        return retVal;
    }

    private String generateReport() {
        log.info("Generating the report.");
        final String tab = "    ";
        StringBuilder sb = new StringBuilder();
        // fetch all the specified input values, put them in order and create a workflow report and possible the script

        ///// INPUT SECTION /////
        sb.append("INPUTS:\n");
        sb.append("================================================\n");
        for (InputSource inputDB : inputDBs) {
            sb.append(inputDB.getSourceDBName());
            List<Filter> filters = inputDB.getFilters();
            if (filters != null && filters.size() > 0) {
                sb.append(" with filters: \n");
                for (Filter filter1 : filters) {
                    sb.append(tab).append("[").append(filter1.getName());
                    if (filter1.getParameter() != null) { // 
                        sb.append(":").append(filter1.getParameter());
                    }
                    sb.append("]\n");
                }
            } else {
                sb.append(" without filters.\n");
            }
        }
        sb.append("\n\n");

        ///// PROCESSING SECTION /////
        // enzyme digestion
        sb.append("PROCESSING:\n");
        sb.append("================================================\n");
        if (enzymeList != null && enzymeList.size() > 0) {
            sb.append("Enzyme digestion:\n");
            for (Enzyme enzyme : enzymeList) {
                sb.append(tab).append("Enzyme: [").append(enzyme.getName());
                sb.append(":").append(enzyme.getCleavePattern());
                sb.append(":").append(enzyme.getRestrictors());
                sb.append(":").append(enzyme.getCNTermAsString());
                sb.append(":").append(enzyme.getMisCleavages());
                sb.append("]\n");
            }
        } else {
            sb.append("No enzyme digestion!\n");
        }
        // ragging
        if (ragging) {
            sb.append(raggingMethod == 0 ? "C" : "N").append("-terminal ragging");
            if (truncate) {
                sb.append(" with truncation to ").append(raggingLimit).append(" N-terminal residues before ragging.\n");
            } else {
                sb.append(".\n");
            }
        } else {
            sb.append("No ragging.\n");
        }
        sb.append("\n\n");

        ///// OUTPUT SECTION /////
        // sequence filter
        sb.append("OUTPUT:\n");
        sb.append("================================================\n");
        if (useSequenceFilter) {
            sb.append("Apply peptide sequence filter: ").append(sequenceFilter).append(".\n");
        } else {
            sb.append("No peptide sequence filtering.\n");
        }
        // mass filter
        if (useMassLimit) {
            sb.append("Peptide mass limit from ").append(massLimitStart).append("Da to ");
            sb.append(massLimitEnd).append(" Da.\n");
        } else {
            sb.append("Don't apply mass limits.\n");
        }

        return sb.toString();
    }


    ///// ///// ///// ///// /////
    // GETTER & SETTER

    public InputSource getSelectDB() {
        return selectDB;
    }

    public void setSelectDB(InputSource selectDB) {
        this.selectDB = selectDB;
    }

    public List<SelectItem> getSourceDBSelectList() {
        return sourceDBSelectList;
    }

    public void setSourceDBSelectList(List<SelectItem> sourceDBSelectList) {
        this.sourceDBSelectList = sourceDBSelectList;
    }

    public int getFilterListSize() {
        if (filterList != null) {
            return filterList.size();
        } else {
            return 0;
        }
    }

    public List<SelectItem> getFilterSelectList() {
        return filterSelectList;
    }

    public void setFilterSelectList(List<SelectItem> filterSelectList) {
        this.filterSelectList = filterSelectList;
    }

    public List<Filter> getFilterList() {
        return filterList;
    }

    public void setFilterList(List<Filter> filterList) {
        this.filterList = filterList;
    }

    public List<InputSource> getInputDBs() {
        return inputDBs;
    }

    public void setInputDBs(List<InputSource> inputDBs) {
        this.inputDBs = inputDBs;
    }

    public int getInputDBsSize() {
        if (inputDBs != null) {
            return inputDBs.size();
        } else {
            return 0;
        }
    }

    public int getEnzymeMethod() {
        return enzymeMethod;
    }

    public void setEnzymeMethod(int enzymeMethod) {
        this.enzymeMethod = enzymeMethod;
    }

    public String getEnzymeName() {
        return enzymeName;
    }

    public void setEnzymeName(String enzymeName) {
        this.enzymeName = enzymeName;
    }

    public String getEnzymeCleave() {
        return enzymeCleave;
    }

    public void setEnzymeCleave(String enzymeCleave) {
        this.enzymeCleave = enzymeCleave;
    }

    public String getEnzymeRestrict() {
        return enzymeRestrict;
    }

    public void setEnzymeRestrict(String enzymeRestrict) {
        this.enzymeRestrict = enzymeRestrict;
    }

    public int getEnzymeMisCleav() {
        return enzymeMisCleav;
    }

    public void setEnzymeMisCleav(int enzymeMisCleav) {
        this.enzymeMisCleav = enzymeMisCleav;
    }

    public int getEnzymeCterm() {
        return enzymeCterm;
    }

    public void setEnzymeCterm(int enzymeCterm) {
        this.enzymeCterm = enzymeCterm;
    }

    public Enzyme getSelectedEnzyme() {
        return selectedEnzyme;
    }

    public void setSelectedEnzyme(Enzyme selectedEnzyme) {
        this.selectedEnzyme = selectedEnzyme;
    }

    public List<SelectItem> getEnzymeSelectList() {
        return enzymeSelectList;
    }

    public void setEnzymeSelectList(List<SelectItem> enzymeSelectList) {
        this.enzymeSelectList = enzymeSelectList;
    }

    public List<Enzyme> getEnzymeList() {
        return enzymeList;
    }

    public void setEnzymeList(List<Enzyme> enzymeList) {
        this.enzymeList = enzymeList;
    }

    public int getEnzymeListSize() {
        if (enzymeList != null) {
            return enzymeList.size();
        } else {
            return 0;
        }
    }

    public boolean isMaturation() {
        return maturation;
    }

    public void setMaturation(boolean maturation) {
        this.maturation = maturation;
    }

    public int getMaturationMethod() {
        return maturationMethod;
    }

    public void setMaturationMethod(int maturationMethod) {
        this.maturationMethod = maturationMethod;
    }

    public boolean getRagging() {
        return ragging;
    }

    public void setRagging(boolean ragging) {
        this.ragging = ragging;
    }

    public int getRaggingMethod() {
        return raggingMethod;
    }

    public void setRaggingMethod(int raggingMethod) {
        this.raggingMethod = raggingMethod;
    }

    public int getRaggingLimit() {
        return raggingLimit;
    }

    public void setRaggingLimit(int raggingLimit) {
        this.raggingLimit = raggingLimit;
    }

    public boolean getTruncate() {
        return truncate;
    }

    public void setTruncate(boolean truncate) {
        this.truncate = truncate;
    }

    public boolean getUseSequenceFilter() {
        return useSequenceFilter;
    }

    public void setUseSequenceFilter(boolean useSequenceFilter) {
        this.useSequenceFilter = useSequenceFilter;
    }

    public String getSequenceFilter() {
        return sequenceFilter;
    }

    public void setSequenceFilter(String sequenceFilter) {
        this.sequenceFilter = sequenceFilter;
    }

    public boolean getUseMassLimit() {
        return useMassLimit;
    }

    public void setUseMassLimit(boolean useMassLimit) {
        this.useMassLimit = useMassLimit;
    }

    public int getMassLimitStart() {
        return massLimitStart;
    }

    public void setMassLimitStart(int massLimitStart) {
        this.massLimitStart = massLimitStart;
    }

    public int getMassLimitEnd() {
        return massLimitEnd;
    }

    public void setMassLimitEnd(int massLimitEnd) {
        this.massLimitEnd = massLimitEnd;
    }

    public boolean isShowReport() {
        return showReport;
    }

    public void setShowReport(boolean showReport) {
        this.showReport = showReport;
    }

    public String getReport() {
        return report;
    }

    public void setReport(String report) {
        this.report = report;
    }

    public String getFormatedReport() {
        return "<pre>" + report + "</pre>";
    }

    public String getReportMessage() {
        return reportMessage;
    }

    public void setReportMessage(String reportMessage) {
        this.reportMessage = reportMessage;
    }

    public String getFormatedReportMessage() {
        return "<pre>" + reportMessage + "</pre>";
    }

    public String getReportView() {
        return reportView;
    }

    public void setReportView(String reportView) {
        this.reportView = reportView;
    }

    public String getWorkflowScript() {
        return workflowScript;
    }

    public String getFormatedScript() {
        return "<pre>" + workflowScript + "</pre>";
    }

    public void setWorkflowScript(String workflowScript) {
        this.workflowScript = workflowScript;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

}
